using System;
using Jink.Core.Converters.Annotations;
using Jink.Core.Json.Converters.Factories;
using Jink.Core.Json.Values;
using Jink.Core.Json.Converters.Shapes.Annotations;
using XenoGears.Functional;
using XenoGears.Assertions;
using System.Linq;

namespace Jink.Core.Json.Converters.Shapes
{
    [Converter]
    internal static class ShapeConverters
    {
        [Converter(Weight = 0.1)]
        public static JsonValue ShapeSerializer<T>(T obj)
        {
            var pbag = obj.ToJsonPbag();
            var seq = obj.ToJsonSeq();
            var map = obj.ToJsonMap();

            (pbag != null || seq != null || map != null).AssertTrue();
            JsonValue json = null;

            if (pbag != null)
            {
                json = new JsonObject(pbag.Properties.Keys.ToDictionary(
                    prop => prop, prop => pbag[prop].ToJson(pbag.Properties[prop])));
            }

            if (seq != null)
            {
                JsonArray array;
                if (map == null)
                {
                    array = new JsonArray(seq.Elements.Cast<Object>()
                        .Select(el => el.ToJson(seq.TElement)));
                }
                else
                {
                    array = new JsonArray(map.Elements.Select(kvp => 
                        new JsonObject(new {
                            key = kvp.Key.ToJson(map.TKey),
                            value = kvp.Value.ToJson(map.TValue),
                        })).Cast<JsonValue>());
                }

                if (json == null)
                {
                    json = array;
                }
                else
                {
                    json["$"] = array;
                }
            }

            return json;
        }

        [Converter(Weight = 0.1)]
        public static T ShapeDeserializer<T>(JsonValue json)
        {
            var obj = JsonFactory.Create<T>();

            var pbag = obj.ToJsonPbag();
            var seq = obj.ToJsonSeq();
            var map = obj.ToJsonMap();

            (pbag != null || seq != null || map != null).AssertTrue();
            (pbag != null).AssertImplies(json is JsonObject);
            (pbag == null).AssertImplies(json is JsonArray);

            if (pbag != null)
            {
                json.Keys.Where(name => name != "$").ForEach(name =>
                    pbag[name] = json[name].FromJson(pbag.Properties[name]));
            }

            if (seq != null)
            {
                var array = (json is JsonObject ? json["$"] : json).AssertCast2<JsonArray>();
                if (map == null)
                {
                    array.Values.ForEach(el => seq.Add(el.FromJson(seq.TElement)));
                }
                else
                {
                    array.Values.ForEach(el =>
                    {
                        var key = el["key"].FromJson(map.TKey);
                        var value = el["value"].FromJson(map.TValue);
                        map.Add(key, value);
                    });
                }
            }

            return obj;
        }
    }
}